%% Author: vivimice
%% Created: Jan 9, 2012
%% Description: TODO: Add description to logger
-module(logger).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([init/0, loop/0]).
-export([trap_error/0]).
-export([error/1, error/2, warn/1, warn/2, info/1, info/2, debug/1, debug/2, log/3]).

%%
%% API Functions
%%
error(Term) when is_number(Term); is_atom(Term) ->
	?MODULE:error("~w", [Term]);
error(Message) ->
	?MODULE:error("~s", [Message]).

error(Format, Messages) ->
	?MODULE:log("ERROR", Format, Messages).

warn(Term) when is_number(Term); is_atom(Term) ->
	?MODULE:warn("~w", [Term]);
warn(Message) ->
	?MODULE:warn("~s", [Message]).

warn(Format, Messages) ->
	?MODULE:log("WARN", Format, Messages).

info(Term) when is_number(Term); is_atom(Term) ->
	?MODULE:info("~w", [Term]);
info(Message) ->
	?MODULE:info("~s", [Message]).

info(Format, Messages) ->
	?MODULE:log("INFO", Format, Messages).

debug(Term) when is_number(Term); is_atom(Term) ->
	?MODULE:debug("~w", [Term]);
debug(Message) ->
	?MODULE:debug("~s", [Message]).

debug(Format, Messages) ->
	?MODULE:log("DEBUG", Format, Messages).

% report error for caller's process
trap_error() ->
	start_link().

%%
%% Local Functions
%%
start_link() ->
	LoggerPid = whereis(logger),
	if 
		is_pid(LoggerPid) -> link(LoggerPid);
		true -> register(logger, spawn_link(?MODULE, init, []))
	end.

init() ->
	process_flag(trap_exit, true),
	?MODULE:loop().

log(TypeString, Format, Messages) ->
	start_link(),
	logger ! {log, self(), TypeString, Format, Messages}.

loop() ->
	receive
		{log, From, TypeString, Format, Messages} ->
			log_i(From, TypeString, Format, Messages);
		
		{'EXIT', From, {Reason, StackTrace}} ->
			log_i(From, "FATAL", "Abnormal exit. Reason: ~s\n~s", [Reason, format_stacktrace(StackTrace)]);
		
		{'EXIT', From, Reason} when Reason /= normal ->
			log_i(From, "FATAL", "Abnormal exit. Reason: ~p\n\t(No stacktrace available)", [Reason]);
		
		_ ->
			ok
	end,
	?MODULE:loop().

log_i(From, TypeString, Format, Messages) ->
	NewFormat = string:concat(string:concat("[~w-~2..0w-~-2..0w ~2..0w:~2..0w:~2..0w][~s][~p] ", Format), "~n"),
	{{Year, Month, Day}, {Hour, Minute, Second}} = erlang:localtime(),
	io:format(NewFormat, lists:append([[Year, Month, Day, Hour, Minute, Second, TypeString, From], Messages])).

format_stacktrace(StackTrace) ->
	format_stacktrace(StackTrace, []).

format_stacktrace([], Acc) ->
	string:join(Acc, "\n");
format_stacktrace([{Module, Function, ArityOrArgs} | Rest], Acc) ->
	format_stacktrace([{Module, Function, ArityOrArgs, []} | Rest], Acc);
format_stacktrace([{Module, Function, ArityOrArgs, SourceInfo} | Rest], Acc) ->
	NewAcc = Acc ++ 
		[io_lib:format(
			   "\tat ~p:~p/~s(~s)", 
			   [Module, Function, format_artiy_args(ArityOrArgs), format_source_info(SourceInfo)])],
	format_stacktrace(Rest, NewAcc).

format_artiy_args(Arity) when is_number(Arity) ->
	integer_to_list(Arity);
format_artiy_args(Args) ->
	io_lib:format("~p", [Args]).

format_source_info([]) ->
	"No source";
format_source_info([{file, File}, {line, Line}]) ->
	File ++ [":"] ++ integer_to_list(Line);
format_source_info(_) ->
	"Unsupported source info".